package com.handlemvc.springmvc.context;

import com.handlemvc.springmvc.annotation.Autowired;
import com.handlemvc.springmvc.annotation.Component;
import com.handlemvc.springmvc.annotation.Controller;
import com.handlemvc.springmvc.annotation.Service;
import com.handlemvc.springmvc.context.beanfactory.InitializingBean;
import com.handlemvc.springmvc.context.xml.XmlParser;
import com.handlemvc.springmvc.util.ClassUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * springmvc容器
 * @author ：tiger.
 * @date ：2021/10/13 00:18
 */
@Slf4j
public class WebApplicationContext {

	private static String CLASS_PATH = "classpath\\:";
	private static String SPLIT_SEPARATOR = ",";
	/**
	 * springmvc 的配置文件地址
	 */
	private final String contextConfigLocation;

	private MvcConfiguration mvcConfiguration;

	private static final List<String> classNames = new ArrayList<>();

	private static final ConcurrentHashMap<String, Object> iocMap = new ConcurrentHashMap<>();

	public WebApplicationContext(String contextConfigLocation) {
		this.contextConfigLocation = contextConfigLocation;
	}

	/**
	 * 刷新容器
	 */
	public void onRefresh() {
		//1、进行解析配置文件操作
		this.parseConfiguration();
		//2、进行包扫描
		this.scanPackages();
		//3、获取实例化装配bean
		this.instanceBean();
		//注入的操作
		this.injectBean();
		//初始化请求映射关系,参照springmvc实现
		this.afterBeanComplete();

	}

	private void parseConfiguration(){
		MvcConfiguration mvcConfiguration = new MvcConfiguration();
		String basePackage = XmlParser.getComponetScanBasePackage(StringUtils.substring(this.contextConfigLocation,this.contextConfigLocation.indexOf(CLASS_PATH)+CLASS_PATH.length()));
		mvcConfiguration.setBasePackage(basePackage);
		this.mvcConfiguration =  mvcConfiguration;
	}

	protected void scanPackages(){
		if(this.mvcConfiguration.getBasePackages()==null || this.mvcConfiguration.getBasePackages().length == 0){
			return;
		}
		for (String basePackage : this.mvcConfiguration.getBasePackages()) {
			scanPackage(basePackage);
		}
	}

	/**
	 * 扫描包
	 * @param basePackage 包路径
	 */
	public void scanPackage(String basePackage){
		URL url = ClassUtils.getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
		if(url== null){
			log.warn("basePackage[{}] is not found",basePackage);
			return;
		}
		String path = url.getFile();
		for (File f: Objects.requireNonNull(new File(path).listFiles())){
			if(f.isDirectory()){
				scanPackage(basePackage+"."+f.getName());
			}else {
				String className = basePackage + "." + f.getName().replaceAll("\\.class", "");
				classNames.add(className);
			}
		}
	}

	@SuppressWarnings("unchecked")
	public void instanceBean() {
		classNames.forEach(className->{
			try {
				Class<?> clazz = Class.forName(className);
				if(!AnnotationTypeFilter.matchAny(clazz,Service.class,Controller.class,Component.class)|| clazz.isInterface() || clazz.isAnnotation()){
					return;
				}
				String beanName = generateBeanName(clazz);
				if(clazz.isAnnotationPresent(Controller.class)){
					Controller annotation = clazz.getAnnotation(Controller.class);
					if(StringUtils.isNotBlank(annotation.value())){
						beanName = annotation.value();
					}
				}else if(clazz.isAnnotationPresent(Service.class)) {
					Service annotation = clazz.getAnnotation(Service.class);
					if(StringUtils.isNotBlank(annotation.value())){
						beanName = annotation.value();
					}
				}
				iocMap.put(beanName, clazz.newInstance());
			} catch (Exception e) {
				log.error("instance bean {} error",className);
			}
		});
	}


	private void injectBean() {
		iocMap.keySet().forEach(beanName->{
			Object bean = iocMap.get(beanName);
			Field[] declaredFields = bean.getClass().getDeclaredFields();
			for (Field declaredField : declaredFields) {
				if(!declaredField.isAnnotationPresent(Autowired.class)){
					continue;
				}
				Autowired annotation = declaredField.getAnnotation(Autowired.class);
				String injectBeanName = StringUtils.isNotBlank(annotation.value())? declaredField.getName():annotation.value();
				if(!iocMap.containsKey(injectBeanName)){
					continue;
				}
				declaredField.setAccessible(true);
				try {
					declaredField.set(bean,iocMap.get(injectBeanName));
				} catch (IllegalAccessException e) {
					log.error("inject bean field {} value {} error",declaredField.getName(),iocMap.get(injectBeanName));
				}
			}
		});
	}

	private void afterBeanComplete(){
		iocMap.values().forEach(bean->{
			if(bean instanceof InitializingBean){
				InitializingBean initializingBean = (InitializingBean) bean;
				try {
					initializingBean.afterPropertiesSet();
				} catch (Exception e) {
					log.error("执行 afterPropertiesSet error",e);
				}
			}
		});
	}

	private String generateBeanName(Class<?> clazz) {
		String simpleName = clazz.getSimpleName();
		return StringUtils.substring(simpleName, 0, 1).toLowerCase() + StringUtils.substring(simpleName, 1);
	}

	public Map<String, Object> getBeans(){
		return Collections.unmodifiableMap(iocMap);
	}

	public static void main(String[] args) {
		WebApplicationContext webApplicationContext = new WebApplicationContext("classpath:webmvc.xml");
		webApplicationContext.onRefresh();
		System.out.println(webApplicationContext);
	}

}
